package com.atguigu.gmall.gateway.filter;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.atguigu.gmall.common.result.Result;
import com.atguigu.gmall.common.result.ResultCodeEnum;
import com.atguigu.gmall.common.util.IpUtil;
import com.atguigu.gmall.gateway.util.AreaInfo;
import com.atguigu.gmall.gateway.util.BaiDuService;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.http.HttpCookie;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.stereotype.Component;
import org.springframework.util.AntPathMatcher;
import org.springframework.util.CollectionUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

import javax.print.attribute.standard.PrinterURI;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author: atguigu
 * @create: 2023-09-12 15:45
 */
@Slf4j
@Component
public class AuthFilter implements GlobalFilter, Ordered {

    AntPathMatcher matcher = new AntPathMatcher();

    @Autowired
    private RedisTemplate redisTemplate;

    /**
     * 需要登录才能访问静态页同步请求
     */
    @Value("${authUrls.url}")
    public List<String> authUrls;

    /**
     * 微服务网关统一鉴权逻辑
     *
     * @param exchange web交换机 封装请求，响应对象
     * @param chain    过滤器链
     * @return
     */
    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        ServerHttpRequest request = exchange.getRequest();
        ServerHttpResponse response = exchange.getResponse();

        log.info("AuthFitler身份认证过滤器执行了");

        //1.获取用户访问请求地址
        String path = request.getURI().getPath();

        //2.对于静态页中js,css,img静态资源-直接放行即可
        if (matcher.match("/css/**", path) || matcher.match("/js/**", path) || matcher.match("/img/**", path)) {
            return chain.filter(exchange);
        }

        //3.对于请求地址中包含"/inner"接口无论登录与否都不允许访问-该接口用于服务间调用
        if (matcher.match("/**/inner/**", path)) {
            //给用户响应错误提示信息-无权访问
            return outError(response, ResultCodeEnum.ILLEGAL_REQUEST);
        }

        //4.尝试获取当前登录用户ID-根据令牌查询Redis来获取
        String userId = this.getUserIdFromRedis(request, response);

        //5.如果用户未登录
        if (StringUtils.isBlank(userId)) {
            //5.1 发起异步请求路径包含"/auth" 响应请登录后访问
            if (matcher.match("/**/auth/**", path)) {
                //说明用户未登录，但是用户访问需要登录才能调用接口
                return outError(response, ResultCodeEnum.LOGIN_AUTH);
            }

            //5.2 发起同步请求静态页请求（我的订单列表，我的个人中心） 引导用户跳转到登录页面
            if (!CollectionUtils.isEmpty(authUrls)) {
                for (String authUrl : authUrls) {
                    if (matcher.match("/" + authUrl, path)) {
                        //需要引导用户进行登录
                        //设置http状态码 303
                        response.setStatusCode(HttpStatus.SEE_OTHER);
                        //设置http响应头 Location 重定向地址
                        response.getHeaders().set(HttpHeaders.LOCATION, "http://passport.gmall.com/login.html?originUrl=" + request.getURI());
                        return response.setComplete();
                    }
                }
            }
        } else {
            //6.如果用户登录，路由转发同时将用户ID设置到请求头中-将用户ID路由到下游微服务中
            request.mutate().header("userId", userId);
        }

        //7.购物车模块可能会产生临时用户ID，尝试获取，如果临时用户ID有值。
        String userTempId = this.getUserTempId(request);
        if (StringUtils.isNotBlank(userTempId)) {
            //将临时用户ID传递到下游购物车微服务
            request.mutate().header("userTempId", userTempId);
        }
        return chain.filter(exchange);
    }


    /**
     * 从客户端提交请求头或者Cookie中获取临时用户ID
     *
     * @param request
     * @return
     */
    private String getUserTempId(ServerHttpRequest request) {
        //1.先从Cookie中获取"userTempId"该Cookie中值 如果有直接返回临时用户ID即可
        HttpCookie cookie = request.getCookies().getFirst("userTempId");
        if (cookie != null) {
            String userTempId = cookie.getValue();
            if (StringUtils.isNotBlank(userTempId)) {
                return userTempId;
            }
        }
        //2.如Cookie未获取到临时用户ID，再尝试从head头中获取 如果有返回
        String userTempId = request.getHeaders().getFirst("userTempId");
        return userTempId;
    }

    /**
     * 根据用户提交Token（请求头或者cookie）查询用户ID-从Redis获取 TODO一定要使用自定义RedisTemplate
     *
     * @param request
     * @param response
     * @return
     */
    private String getUserIdFromRedis(ServerHttpRequest request, ServerHttpResponse response) {
        //1.获取客户端提交Token令牌
        String token = "";
        //1.1 尝试从请求头中"token"获取令牌
        token = request.getHeaders().getFirst("token");
        //1.2 如果请求头中没有提交，再从Cookie中通过Cookie名称"token"获取
        if (StringUtils.isBlank(token)) {
            if (!ObjectUtils.isEmpty(request.getCookies())) {
                HttpCookie cookie = request.getCookies().getFirst("token");
                if (cookie != null) {
                    token = cookie.getValue();
                }
            }
        }
        //2.查询用户信息
        if (StringUtils.isNotBlank(token)) {
            //2.1 使用token拼接获取用户Key
            String loginKey = "user:login:" + token;
            //2.2 查询Redis
            JSONObject userObj = (JSONObject) redisTemplate.opsForValue().get(loginKey);
            if (userObj != null) {
                //3.查询Redis中用户信息-判断用户所在城市是否发生改变
                String userIp = IpUtil.getGatwayIpAddress(request);
                try {
                    AreaInfo area = BaiDuService.getArea(userIp);
                    if (!userObj.get("city").equals(area.getCity())) {
                        //存在风险 删除令牌 发送预警短信
                        redisTemplate.delete(loginKey);
                        return null;
                    }
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }

                //4.判断令牌key过期时间，如果小于规定阈值（小于6小时）再次对Key过期时间进行设置
                Long expire = redisTemplate.getExpire(loginKey);
                if (expire <= 60 * 60 * 6) {
                    redisTemplate.expire(loginKey, 24, TimeUnit.HOURS);
                }
                return userObj.getString("id");
            }
        }
        return null;
    }


    /**
     * 用来给前端响应错误提示信息
     *
     * @param response
     * @param resultCodeEnum
     * @return
     */
    private Mono<Void> outError(ServerHttpResponse response, ResultCodeEnum resultCodeEnum) {
        //1.准备响应结果对象,转为JSON对象
        Result<Object> result = Result.build(null, resultCodeEnum);
        String resultString = JSON.toJSONString(result);

        //2.响应结果给客户端
        //2.1 设置http状态码
        response.setStatusCode(HttpStatus.UNAUTHORIZED);
        //2.2 通过响应头设置响应数据格式-json
        response.getHeaders().add("content-type", "application/json;charset=utf-8");
        DataBuffer wrap = response.bufferFactory().wrap(resultString.getBytes());
        //2.4 网关将响应数据返回给客户端
        return response.writeWith(Mono.just(wrap));
    }

    /**
     * 过滤器链中执行顺序
     *
     * @return
     */
    @Override
    public int getOrder() {
        return -1;
    }

    public static void main(String[] args) {
        //String userPath = "/a/b/css/all.css";
        //String userPath = "/api/product/inner/getCategoryView/61";
        String userPath = "/trade.html";

        AntPathMatcher matcher = new AntPathMatcher();
        boolean match = matcher.match("/trade.html", userPath);
        System.out.println(match);
    }
}
